import dragOperationSlice from '../redux/toolKitSlice/dragOperation';
import {
  getEventClientOffset,
  getNodeClientOffset,
} from '../utils/OffsetUtils';
import type { DragDropManager, HandlerRegistry, XYCoord } from './interface';

class DragDropManagerImpl implements DragDropManager {
  private store: any;
  private registry: HandlerRegistry;
  private dragStartHandlerId: string = ''; //记录当前正在拖动元素对应的dnd handlerId
  private overTargetId: string = '';
  public constructor(store: any, registry: HandlerRegistry) {
    this.store = store;
    this.registry = registry;
    this.setup();
  }

  public getRegistry: () => HandlerRegistry = () => {
    return this.registry;
  };

  //-----------------Drag相关(事件绑定/sotre更新)----------------------------

  private setup() {
    // console.log('---0--DragDropManagerImpl只能实例化一次--setup--')
    //dragNode.dragstart --冒泡--window.dragstart
    window.addEventListener('dragstart', (e: DragEvent) => {
      this.handleTopDragStart(e);
    });
    window.addEventListener('dragend', this.handleTopDragEndCapture, true);
    // window.addEventListener(
    // 	'dragenter',
    // 	this.handleTopDragEnter as EventListener,
    // )
    window.addEventListener('dragover', (e: DragEvent) => {
      this.handleTopDragOver(e);
    });
    // window.addEventListener('drop', (e:DragEvent) => {this.handleTopDrop(e)})
  }

  public removeEventListeners() {
    window.removeEventListener('dragstart', (e: DragEvent) => {
      this.handleTopDragStart(e);
    });
    window.removeEventListener('dragend', this.handleTopDragEndCapture, true);
    window.removeEventListener('dragover', (e: DragEvent) => {
      this.handleTopDragOver(e);
    });
    // window.removeEventListener('drop', (e:DragEvent) => {this.handleTopDrop(e)})
  }

  //dragStart时更新store
  private handleTopDragStart = (e: DragEvent) => {
    if (!this.dragStartHandlerId) return;
    // currentTarget:事件绑定的对象(是唯一的)
    // const currDragNode = e.currentTarget!; //window
    const itemType = this.registry.getSourceType(this.dragStartHandlerId)!;
    const dragSource = this.registry.getSource(this.dragStartHandlerId)!;
    const sourceDragNode = dragSource.getDragNode();
    const item = dragSource.getItem();
    const dragStartNodeClientOffset = getNodeClientOffset(sourceDragNode!);
    const dragStartEventClientOffset = getEventClientOffset(e);
    this.store.dispatch(
      dragOperationSlice.actions.beginDrag({
        itemType,
        item,
        sourceId: this.dragStartHandlerId,
        dragStartNodeClientOffset,
        dragStartEventClientOffset,
      }),
    );
    // console.log('--start--0.3--handleTopDragStart--this.dragStartHandlerId--:',this.dragStartHandlerId);
    setTimeout(() => {
      this.store.dispatch(dragOperationSlice.actions.publishDragSource({})); // opactity === isDragging ? 0 : 1, isDragging立马true导致 drag时浏览器自导的预览dom opacity === 0
    });
  };

  private handleDragStart = (e: DragEvent, sourceId: string) => {
    // console.log('--start--0.2--handleDragStart--sourceId--:',sourceId)
    const dragSource = this.registry.getSource(sourceId);
    if (!dragSource) {
      throw new Error('handleDragStart： dragSource 不存在');
    }
    if (!dragSource.canDrag()) {
      e.stopPropagation(); //阻止冒泡
      e.preventDefault(); //阻止默认行为，后续的dragEnter事件无法执行
    }
    this.dragStartHandlerId = sourceId;
  };

  //dragEnd,只要mouseup(放开鼠标左键)都会执行dragEnd
  private handleTopDragEndCapture = () => {
    const isNotDragging = this.isNotDragging();
    // console.log('--dragEnd--5--handleTopDragEndCapture--isNotDragging--:',isNotDragging);
    if (isNotDragging) {
      return;
    }
    const dragSource = this.registry.getSource(this.dragStartHandlerId);

    if (!dragSource) {
      throw new Error('handleDragend: dragSource 不存在');
    }
    dragSource.endDrag();
    this.dragStartHandlerId = '';
    this.store.dispatch(dragOperationSlice.actions.endDrag({})); // 清空正在拖拽源的数据
  };

  // dargNode绑定dragStart事件
  public bindDragEventsForDragNode: (
    sourceId: string,
    dragNode: HTMLElement,
  ) => () => void = (sourceId, dragNode) => {
    const handleDragStart = (e: DragEvent) => this.handleDragStart(e, sourceId);
    dragNode.setAttribute('draggable', 'true');
    dragNode.addEventListener('dragstart', handleDragStart, false); //默认false,冒泡形式进行事件传递
    return () => {
      dragNode.removeEventListener('dragstart', handleDragStart, false);
      dragNode.setAttribute('draggable', 'false');
    };
  };

  //-----------------Drop相关(事件绑定/sotre更新)----------------------------

  private handleDragEnter(e: DragEvent, targetId: string) {
    const isNotDragging = this.isNotDragging();
    // console.log('--enter--1.2--handleDragEnter--this.dragStartHandlerId--isNotDragging--:',this.dragStartHandlerId,isNotDragging)
    if (isNotDragging) {
      // if (e.dataTransfer) {
      // e.dataTransfer.dropEffect = 'none'
      // }
      return;
    }
    if (targetId) {
      const canDrop = this.canDropOnTarget(targetId);
      const registry = this.getRegistry();
      const dropTarget = registry.getTarget(targetId);
      if (!dropTarget) {
        throw new Error('handleDragEnter:dorpTarget不存在');
      }
      const dropNode = dropTarget.getDropNode();
      const dropEventClientOffset = getEventClientOffset(e);
      const enterNodeClientOffset = getNodeClientOffset(dropNode!);
      this.store.dispatch(
        dragOperationSlice.actions.over({
          targetId,
          dropEventClientOffset: dropEventClientOffset,
          dropNodeClientOffset: enterNodeClientOffset,
        }),
      );
      dropTarget.hover();
      if (canDrop) {
        // if (e.dataTransfer) {
        // e.dataTransfer.dropEffect = 'move'
        // }
      }
    }
  }

  /**
   *
   *
   * @private
   * @memberof DragDropManagerImpl
   * @description  handleTopDragEnter可以脱离handleDragEnter单独多次执行,单独执行时this.dragStartHandlerId === undefined，导致出bug
   */
  // private handleTopDragEnter = (e: DragEvent): void => {
  // const currEnterTargetId = this.enterTargetId;
  // this.enterTargetId = '';

  // }

  private handleDragOver(e: DragEvent, targetId: string) {
    this.overTargetId = targetId;
    // // console.log('--hover--2.2--handleDragOver--');
  }

  /**
   *
   *
   * @private
   * @memberof DragDropManagerImpl
   * @description 当 dragSource被拖得太远脱离dropTarget时， handleTopDragOver脱离handleDragOver单独多次执行,单独执行时this.dragStartHandlerId === undefined，导致出bug
   * @description
   */
  private handleTopDragOver = (e: DragEvent): void => {
    const currOverTargetId = this.overTargetId;
    this.overTargetId = '';
    if (currOverTargetId) {
      //handleTopDragOver单独执行时,currOverTargetId === undefined,此时 不执行执行drop事件
      // // console.log('--hover--2.3--handleTopDragOver--this.dragStartHandlerId--isNotDragging--:',this.dragStartHandlerId,this.isNotDragging());
      const isNotDragging = this.isNotDragging();
      if (isNotDragging) {
        // if (e.dataTransfer) {
        //   e.dataTransfer.dropEffect = 'none'
        // }
        return;
      }

      const canDrop = this.canDropOnTarget(currOverTargetId);
      const registry = this.getRegistry();
      const dropTarget = registry.getTarget(currOverTargetId);
      if (!dropTarget) {
        throw new Error('handleTopDragOver:dorpTarget不存在');
      }
      const dropEventClientOffset = getEventClientOffset(e);
      const dropNode = dropTarget.getDropNode();
      const overNodeClientOffset = getNodeClientOffset(dropNode!);
      this.store.dispatch(
        dragOperationSlice.actions.over({
          targetId: currOverTargetId,
          dropEventClientOffset: dropEventClientOffset,
          dropNodeClientOffset: overNodeClientOffset,
        }),
      );
      dropTarget.hover();
      // // console.log('--hover--2.3--handleTopDragOver--canDrop--:',canDrop);
      if (canDrop) {
        //指的是是否执行drop事件
        //dragOver时：默认情况下，不能在其他元素中删除数据/元素。为了允许放置(执行onDrop事件)，我们必须阻止元素的默认处理
        e.preventDefault();
        // if (e.dataTransfer) {
        //   e.dataTransfer.dropEffect = 'move';
        // }
      } else {
        // if (e.dataTransfer) {
        //   e.dataTransfer.dropEffect = 'none';  //有回弹效果
        // }
      }
    } else {
      // // console.log('--hover--2.3--dragSource被拖得太远脱离dropTarget时---');
      //注：dragSource被拖得太远找不到dropTarget时,放开鼠标dragSource默认执行回弹动画到执行dragStart时的位置
      // 现在 以下设置取消回弹 （dropEffect = 'move),当找到dropTarget时允许执行drop事件（e.preventDefault()，是为了兼容排序因为排序找不到dropTarget时，也会改变（useDrop.hover()）dom顺序
      // 发散： 取消handleTopDragOver事件，直接handleDragOver完成相关逻辑，那么一旦dragSource被拖得太远找不到dropTarget时，handleDragOver不会执行，放开鼠标dragSource默认执行回弹动画到执行dragStart时的位置
      // e.preventDefault();
      // if (e.dataTransfer) {
      //   e.dataTransfer.dropEffect = 'move'
      // }
    }
  };

  //TODO:有需要的时候再加
  private handleDragLeave(e: DragEvent, targetId: string) {}

  private handleDrop = (e: DragEvent, targetId: string): void => {
    const isNotDragging = this.isNotDragging();
    // console.log('--drop--4.2--handleDrop--handleTopDrop--isNotDragging--this.dragStartHandlerId--:',this.dragStartHandlerId,isNotDragging);

    if (isNotDragging) {
      return;
    }
    e.preventDefault(); //浏览器对drop事件的默认行为是以链接形式打开
    const registry = this.getRegistry();
    const dropTarget = registry.getTarget(targetId);
    if (!dropTarget) {
      throw new Error('handleDrop:dorpTarget不存在');
    }
    const dropNode = dropTarget.getDropNode();
    const dropNodeClientOffset = getNodeClientOffset(dropNode!);
    const dropEventClientOffset = getEventClientOffset(e);
    this.store.dispatch(
      dragOperationSlice.actions.over({
        targetId,
        dropEventClientOffset: dropEventClientOffset,
        dropNodeClientOffset,
      }),
    );

    const dropResult = dropTarget.drop();
    this.store.dispatch(
      dragOperationSlice.actions.drop({ targetId: targetId, dropResult }),
    );
  };

  // private handleTopDrop = (e: DragEvent):void => {
  //   // console.log('--4.3--handleTopDrop--:')
  // }

  public bindEventsForDropNode: (
    targetId: string,
    dropNode: HTMLElement,
  ) => () => void = (targetId, dropNode) => {
    dropNode.addEventListener('dragenter', (e: DragEvent) => {
      this.handleDragEnter(e, targetId);
    });
    dropNode.addEventListener('dragover', (e: DragEvent) => {
      this.handleDragOver(e, targetId);
    });
    dropNode.addEventListener('dragleave', (e: DragEvent) => {
      this.handleDragLeave(e, targetId);
    });
    dropNode.addEventListener('drop', (e: DragEvent) => {
      this.handleDrop(e, targetId);
    });

    return () => {
      dropNode.removeEventListener('dragenter', (e: DragEvent) => {
        this.handleDragEnter(e, targetId);
      });
      dropNode.removeEventListener('dragover', (e: DragEvent) => {
        this.handleDragOver(e, targetId);
      });
      dropNode.removeEventListener('dragleave', (e: DragEvent) => {
        this.handleDragLeave(e, targetId);
      });
      dropNode.removeEventListener('drop', (e: DragEvent) => {
        this.handleDrop(e, targetId);
      });
    };
  };

  // store更新时更新useDrag/useDrop的第一个参数
  public subscribeStateChange: (listenerFn: () => void) => void = (
    listenerFn,
  ) => {
    //最终listenerFn传递给了redux仓库
    this.store.subscribe(() => {
      // // console.log('--subscribeStateChange--this.dragStartHandlerId--:',this.dragStartHandlerId)
      listenerFn(); //handleTopDragEndCapture--也会触发此时this.dragStartHandlerId === undefined, 且所有Card collect()会重新执行
    });
  };

  //-----------------监听器monitor相关----------------------------

  /**
   *
   *
   * @param {string} currSourceId
   * @returns {boolean}
   * @memberof DragDropManagerImpl
   * @description 用于monitor.isDragging
   */
  public isDraggingSource(currSourceId: string): boolean {
    const dragOperaationStore = this.store.getState().dragOperationStore;
    const { sourceId, isSourcePublic } = dragOperaationStore;
    // // console.log('--Card--monitor.isDragging()--isSourcePublic--currSourceId--sourceId--:',isSourcePublic,currSourceId,sourceId);
    if (!isSourcePublic) return false;
    return currSourceId === sourceId;
  }

  private isNotDragging(): boolean {
    const dragOperaationStore = this.store.getState().dragOperationStore;
    const { sourceId, isSourcePublic } = dragOperaationStore;
    const isNotDragging =
      this.dragStartHandlerId !== sourceId || !isSourcePublic ? true : false;
    return isNotDragging;
  }

  public getDragStartNodeClientOffset(): XYCoord | undefined {
    return this.store.getState().dragOperationStore.dragStartNodeClientOffset;
  }

  public getDragStartEventClientOffset(): XYCoord | undefined {
    return this.store.getState().dragOperationStore.dragStartEventClientOffset;
  }

  //----drop----

  public getDragType(): string {
    const dragOperaationStore = this.store.getState().dragOperationStore;
    const { itemType } = dragOperaationStore;
    return itemType;
  }

  public getDragItem(): any {
    const dragOperaationStore = this.store.getState().dragOperationStore;
    return dragOperaationStore.item;
  }

  public canDropOnTarget(targetId: string) {
    if (!targetId) {
      return false;
    }
    if (this.isNotDragging()) {
      return false;
    }
    const target = this.registry.getTarget(targetId);
    if (!target) {
      throw new Error('canDropOnTarget:dorpTarget不存在');
    }
    const accept = target.getAccept();
    const dragItemType = this.getDragType();
    if (Array.isArray(accept)) {
      if (accept.indexOf(dragItemType) < 0) {
        return false;
      }
    } else if (accept !== dragItemType) {
      return false;
    }
    const canDrop = target.canDrop();

    if (!canDrop) {
      return false;
    }

    return true;
  }

  public isOverTarget: (targetId: string) => boolean = (targetId: string) => {
    if (!targetId) {
      return false;
    }

    if (this.isNotDragging()) {
      return false;
    }

    const dragOperaationStore = this.store.getState().dragOperationStore;
    const { targetId: dropingTargetId } = dragOperaationStore || {};
    if (!dropingTargetId || dropingTargetId !== targetId) {
      return false;
    }

    return true;
  };

  public getDropResult(): any {
    return this.store.getState().dragOperationStore.dropResult;
  }

  public getDropEventClientOffset(): XYCoord | undefined {
    return this.store.getState().dragOperationStore.dropEventClientOffset;
  }

  public getDropNodeClientOffset(): XYCoord | undefined {
    return this.store.getState().dragOperationStore.dropNodeClientOffset;
  }

  public updateDragItem(newItem: any): void {
    this.store.dispatch(dragOperationSlice.actions.updateItem(newItem));
  }
}

export default DragDropManagerImpl;
